/**
 * 页面切换
 */
import { version } from '../package.json'
import { GT, ST, GT_A, ST_A, getLocation, getPageDetail, getNowDate } from './utils'

// 页面链相关
const getChain = () => GT_A('SXYJ_ROUTE_CHAIN')
const setChain = value => ST_A('SXYJ_ROUTE_CHAIN', value)

// 页面栈相关
const getStack = () => GT_A('SXYJ_ROUTE_STACK')
const setStack = value => ST_A('SXYJ_ROUTE_STACK', value)
const getStackHistory = () => GT_A('SXYJ_ROUTE_STACK_HISTORY')
const setStackHistory = value => ST_A('SXYJ_ROUTE_STACK_HISTORY', value)

// history state 额外参数
let SXYJ_ROUTE_ID = 0
const getRouteID = () => GT('SXYJ_ROUTE_ID') || 0
const setRouteID = () => ST('SXYJ_ROUTE_ID', ++SXYJ_ROUTE_ID) || true


/**
 * @method Route
 * @desc 页面跳转监听
 * @param {Number} chainLength -> 页面链长度
 * @param {Boolean} getLastPageDetail -> 展示返回参数详情 -> lastChainDetail lastStackDetail
 * @param {Function} startCallback -> 将首次执行添加页面传入外部，由外部异步调用
 * 
 * @desc 若在两个页面内互相跳转，且前进后退则stack无法判断 - hash跳转，location跳转（history跳转已判断）
 * @desc 例如：A 跳到 B, B 跳到 A, A 跳到 B，则此时页面栈为 [A, B, A]，在此点击返回，页面栈为 [A, B] 历史页面栈为 [A]，在这时，前进或者后退，下一个页面都是 A 页面，此时无法判断是前进还是后退，默认给定 后退 操作
 *
 * @desc 使用 location.replace 进行跳转 同样无法判断，默认给定 href相同 操作
 */
class Route {
	constructor({ chainLength = 6, getLastPageDetail = false, startCallback = null, rightAway = false } = {}) {
		// 配置项
		this._options = {
			version, // 版本
			chainLength,
			getLastPageDetail,
		}
		// 页面栈历史
		this._pageStackHistory = getStackHistory()
		// 钩子函数列表
		this._afterEachList = []
		this._afterEachKey = 0
		// 页面链
		this._chain = getChain()
		// 页面栈
		this._pageStack = getStack()

		// 当前页面 getPageDetail(url) 返回参数
		this.currentPage = {}
		// 父级页面 与 currentPage 一致
		this.parentPage = {}
		// 外界options
		this.options = {
			// 页面链附件参数
			pageChainParams: {},

			// 页面栈附加参数
			pageStackParams: {},

			rightAway,
		}

		// 初始化
		this.init(startCallback)
	}
	
	/**
	 * @method getOwnPageStack
	 * @desc 获取页面栈
	 * @return 页面栈
	 */
	getOwnPageStack() {
		return this._pageStack.map(item => item.url)
	}

	/**
	 * @method getOwnPageStackParams
	 * @desc 获取页面栈其他参数
	 * @param {String} key 页面栈其他参数中对象的指定的key
	 * @param {Number} index 指定下标的页面栈参数
	 * @return 页面栈其他参数
	 */
	getOwnPageStackParams(key, { index } = {}) {
		const list = typeof index === 'number' ? this._pageStack.slice(index, index + 1) : this._pageStack

		return JSON.parse(JSON.stringify(list.map(item => {
			const { url, ...params } = item

			return (key !== void 0 && key !== null) ? params[key] : params
		})))
	}

	/**
	 * @method getOwnChain
	 * @desc 获取页面链
	 * @return 页面链
	 */
	getOwnChain() {
		return this._chain.map(item => item.url)
	}

	/**
	 * @method getOwnPageChainParams
	 * @desc 获取页面链其他参数
	 * @param {String} key 页面链其他参数中对象的指定的key
	 * @param {Number} index 指定下标的页面链参数
	 * @return 页面链其他参数
	 */
	getOwnPageChainParams(key, { index } = {}) {
		const list = typeof index === 'number' ? this._chain.slice(index, index + 1) : this._chain

		return JSON.parse(JSON.stringify(list.map(item => {
			const { url, ...params } = item

			return (key !== void 0 && key !== null) ? params[key] : params
		})))
	}

	/**
	 * @method addChain
	 * @desc 添加页面链，可手动添加
	 * @param {Object} value -> 参数如下
	 * @param {String} value.newUrl -> 新的路径，可不传，不传时添加当前路径
	 * @param {String} value.isRefresh -> 是否为刷新当前页面
	 */
	addChain({ isRefresh = false, newUrl = '' } = {}) {
		const chainData = { url: newUrl || getLocation().href, date: getNowDate(), pageChainParams: JSON.parse(JSON.stringify(this.options.pageChainParams)) }
		
		const chains = this.getOwnChain()
		this.parentPage = getPageDetail(chains.length === 1 && isRefresh ? '' : chains[isRefresh ? 1 : 0]) // this.currentPage
		this.currentPage = getPageDetail(chainData.url)

		if (isRefresh) {
			// 刷新页面则更新当前页面链
			// 保留部分状态
			chainData.date = this._chain[0].date

			this._chain.splice(0, 1, chainData)
		}
		else this._chain.unshift(chainData)

		// 页面链长度修正
		if (this._chain.length > this._options.chainLength) this._chain.splice(this._options.chainLength)

		// 缓存
		setChain(this._chain)
	}

	/**
	 * @method updateStack
	 * @desc 修改页面栈 
	 * @param {Number} relative -> 相对当前页面跳转的页面量
	 * @param {Boolean} native -> 是否是浏览器原生操作
	 * @param {Object} state -> 页面其他参数
	 */
	updateStack(relative = 0, native = false, { state = {}, firstEnter = false, isRefresh = false } = {}) {
		const stackData = { url: getLocation().href, date: getNowDate(), state, firstEnter, pageStackParams: JSON.parse(JSON.stringify(this.options.pageStackParams)) }

		// 前进到新的页面则移除栈历史
		if (relative > 0 && !native) this._removeStackHistory()

		if (relative > 0 && (native ? relative <= this._pageStackHistory.length : true)) {
			// 添加页面 或 前进
			new Array(Math.abs(relative)).fill(1).forEach(() => this._pageStack.unshift(native ? this._pageStackHistory.shift() || stackData : stackData))
		} else if (relative === 0) {
			// 重定向

			// 刷新页面保留部分状态
			if (isRefresh) stackData.date = this._pageStack[0].date

			this._pageStack[0] = stackData
		} else if (relative < 0 && -relative < this._pageStack.length) {
			// 后退
		 	new Array(Math.abs(relative)).fill(1).forEach(() => this._pageStackHistory.unshift(this._pageStack.shift()))
		}

		// 页面栈长度修正
		const max = this._options.chainLength * 2
		if (this._pageStack.length > max) this._pageStack.splice(max)
		if (this._pageStackHistory.length > max) this._pageStackHistory.splice(max)

		// 缓存
		setStack(this._pageStack)
		setStackHistory(this._pageStackHistory)
	}

	/**
	 * @method afterEach
	 * @desc 添加页面跳转钩子函数
	 * @param {Function} callback -> 函数
	 * @return {Function} 执行该函数可以关闭对应的钩子函数
	 */
	afterEach(callback) {
		const key = this._afterEachKey++
		
		this._afterEachList.push({
			key,
			value: callback,
		})

		return () => this._afterEachList = this._afterEachList.filter(item => item.key !== key)
	}

	/**
	 * @method _setTimeout
	 * @desc 延时
	 * @param {Function} callback -> 函数
	 * @param {Number} time 时间（毫秒）
	 * @return setTimeout
	 */
	_setTimeout(callback, time = 4) {
		return setTimeout(callback, time)
	}

	/**
	 * @method _removeStackHistory
	 * @desc 删除栈历史
	 * @return StackHistory
	 */
	_removeStackHistory() {
		return this._pageStackHistory.splice(0, this._pageStackHistory.length)
	}

	/**
	 * @method init
	 * @desc 初始化
	 */
	init(startCallback) {
		// 初始化
		const _this = this

		// 初始化 SXYJ_ROUTE_ID
		SXYJ_ROUTE_ID = getRouteID()

		// 触发钩子
		const useRouteChange = ({
			type = 'before',
			relative = 0, // 页面便宜
			isRefresh = false, // 刷新页面
			native = false, // 原生操作
			state = {},
			newUrl = '',
			firstEnter = false,
		} = {}) => {
			// 添加页面链
			this.addChain({ isRefresh, newUrl })
			// 更新页面栈
			this.updateStack(relative, native, { state, firstEnter, isRefresh })

			// 当前页面更新后执行钩子函数
			this._afterEachList.forEach(item => typeof item.value === 'function' && item.value(Object.assign(
				{
					type, // 在页面跳转之前发送
					detail: getPageDetail(getLocation().href),
					chain: this.getOwnChain(), // 页面链
					pageStack: this.getOwnPageStack(), // 页面栈
					relative, // 相对当前页面跳转的页面数量
					state, // history页面参数
					params: {
						chain: this.getOwnPageChainParams(null, { index: 0 })[0],
						stack: this.getOwnPageStackParams(null, { index: 0 })[0],
					}
				},
				this._options.getLastPageDetail ? {
					lastChainDetail: getPageDetail(this._chain[1] && this._chain[1].url),
					lastStackDetail: getPageDetail(this._pageStack[1] && this._pageStack[1].url),
					lastParams: {
						chain: this.getOwnPageChainParams(null, { index: 1 })[0] || {},
						stack: this.getOwnPageStackParams(null, { index: 1 })[0] || {},
					}
				} : {}
			)))

			return true
		}

		// history 代理
		const { back, forward, go, pushState, replaceState } = window.History.prototype
		const kv = {
			back: [back, -1],
			forward: [forward, 1],
			pushState: [pushState, 1],
			replaceState: [replaceState, 0],
			go: [go, 0],
		}
		// 不会触发 popstate 的方法名
		const unPopChange = ['pushState', 'replaceState']
		let history_popStateFlag = false

		Object.keys(kv).forEach(item => {
			window.History.prototype[item] = function () {
				// 路径改变后添加页面链
				const oldHref = getLocation().href
				const _SXYJ_ROUTE_ID = SXYJ_ROUTE_ID

				_this._setTimeout(() => {
					const newHref = getLocation().href
					// 页面未改变，无效的操作
					if (oldHref === newHref) return

					useRouteChange({
						type: 'after',
						relative: item === 'go' ? (arguments[0] || 0) : kv[item][1],
						...Object.assign(_SXYJ_ROUTE_ID < SXYJ_ROUTE_ID ? { state: { ...arguments[0], _SXYJ_ROUTE_ID } } : {}), // 执行 pushState replaceState 传入额外参数state
					})
				})
				
				// back forward go 会触发 popstate，在这做判断浏览器原生返回前进
				if (!unPopChange.find(_item => _item === item)) history_popStateFlag = true
				else return setRouteID() && kv[item][0].apply(this, [{ ...arguments[0], _SXYJ_ROUTE_ID }, ...[].slice.call(arguments, 1)])

				return kv[item][0].apply(this, arguments)
			}
		})
		
		// replace 无法代理，弃用
		// replace 代理 
		// const { replace } = window.location
		// let hash_popStateFlag = 0 // 0 -> 初始， 1 -> hash相等， 2 -> hash不等
		
		// window.location.replace = function (url) {
		// 	// hash 跳转，不刷新页面
		// 	const hash = url.split('#')[1]
		// 	if (hash) {
		// 		const chainHash = _this._chain[0].url.split('#')[1]

		// 		// 无论是否相等都会触发 popstate
		// 		if (hash === chainHash) hash_popStateFlag = 1
		// 		// 不等则 relative 为 0
		// 		else hash_popStateFlag = 2
		// 	}
			
		// 	return replace.apply(this, arguments)
		// }

		// popstate 与 hashchange 都会触发，弃用hashchange
		// 监听跳转 hash
		// window.addEventListener('hashchange', e => {
		// 	this.addChain({ newUrl: e.newURL })
		// 	console.log('hashchange', e)
			
		// 	useRouteChange({
		// 		type: 'after',
		// 		relative: 1,
		// 	})
		// })

		// 监听浏览器原生返回
		window.addEventListener('popstate', e => {
			if (history_popStateFlag/*  || hash_popStateFlag === 1 */) {
				history_popStateFlag = false
				// hash_popStateFlag = 0
			} else {
				const { href } = getLocation()
				// 相同页无跳转
				if (href === this._chain[0].url) return

				const _SXYJ_ROUTE_ID = e.state ? e.state.SXYJ_ROUTE_ID : void 0
				// 若 hash 不等，则为前进
				const native = href.split('#')[1] === this._chain[0].url.split('#')[1] ? true : false

				this._setTimeout(() => useRouteChange({
						type: 'after',
						// 页面栈的上一个页面与当前已经到达的页面是同一个地址，则判断为原生的后退操作
						// 使用 SXYJ_ROUTE_ID 加强在 SPA 内 pushState replaceState 跳转的准确性
						relative: 
							// hash_popStateFlag === 2 ? (hash_popStateFlag = 0) :
								this._pageStack[1] ? 
								_SXYJ_ROUTE_ID !== void 0 ? 
									(+this._pageStack[1].state.SXYJ_ROUTE_ID === _SXYJ_ROUTE_ID ? -1 : 1) : 
									(this._pageStack[1].url === href ? -1 : 1) : 
								1,
						native,
					}))
			}
		})

		// 首次添加页面
		const startAddChain = () => {
			// 在这做判断，有可能是后退操作

			// 上一个页面的当前页面 与 当前载入页面 相同，则为返回
			const { href } = getLocation()
			const isRefresh = this._pageStack[0] && this._pageStack[0].url === href
			const relative = isRefresh ? 0 : (this._pageStack[1] && this._pageStack[1].url === href) ? -1 : 1

			useRouteChange({
				type: 'before',
				relative,
				isRefresh,
				firstEnter: true,
			})
		}

		// 进入页面添加当前页面，如果传入了异步回调，则将首次添加交给外部调用
		typeof startCallback === 'function' ? startCallback(startAddChain) : this.options.rightAway ? startAddChain() : this._setTimeout(startAddChain)
	}
}

// 单例模式
export default function(...reg) { return window._YQ_WATCH_ROUTE_ || (window._YQ_WATCH_ROUTE_ = new Route(...reg)) }